using System;
using System.Linq;
using System.Reflection;
using Microsoft.SharePoint;
using SharePoint.Orm.Helpers;

namespace SharePoint.Orm {
	public class DefaultValueTypeConverter : IValueTypeConverter {
		public virtual T FromListItemValue<T>(SPDataContext ctx, SPListItem listItem, Guid fieldId, object val) {
			// handle nulls
			if (IsNullable(typeof(T)) && val is string) {
				if (string.IsNullOrEmpty((string)val)) {
					return default(T);
				}
			}
			if (val == null) {
				return default(T);
			}

			// handle T's with constructors that match val type
			T result;
			if (TryInvokeContructor(listItem.Web, typeof(T), val, out result)) {
				return result;
			}

			if (listItem.Fields[fieldId].GetType() == typeof(SPFieldLookup)) {
				return GetLookupObject<T>(ctx, listItem, fieldId, new SPFieldLookupValue((string)val));
			}

			return TypeSystem.Cast<T>(val);
		}

		private T GetLookupObject<T>(SPDataContext ctx, SPItem listItem, Guid fieldId, SPFieldLookupValue val) {
			SPFieldLookup lookupField = (SPFieldLookup)listItem.Fields[fieldId];
			Guid lookupListId = new Guid(lookupField.LookupList);
			IQueryable list = ctx.GetListByListId(lookupListId);
			return (T)ctx.GetListItemById(list, val.LookupId);
		}

		private bool IsNullable(Type type) {
			return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
		}

		private bool TryInvokeContructor<T>(SPWeb site, Type type, object val, out T result) {
			if (IsNullable(type)) {
				type = type.GetGenericArguments()[0];
			}

			// try single argument constructor
			ConstructorInfo constructorInfo = type.GetConstructor(new[] { val.GetType() });
			if (constructorInfo != null) {
				result = (T)constructorInfo.Invoke(new[] { val });
				return true;
			}

			// try (SPWeb, val) constructor
			constructorInfo = type.GetConstructor(new[] { typeof(SPWeb), val.GetType() });
			if (constructorInfo != null) {
				result = (T)constructorInfo.Invoke(new[] { site, val });
				return true;
			}

			result = default(T);
			return false;
		}

		public virtual object ToListItemValue<T>(SPDataContext ctx, SPListItem listItem, Guid fieldId, T value) {
			if (listItem.ParentList.Fields[fieldId].GetType() == typeof(SPFieldLookup)) {
				SPFieldLookupValue spFieldLookup = value as SPFieldLookupValue;
				if (spFieldLookup != null) {
					return spFieldLookup;
				}
				
				ISPListItemAware valueListItemAware = (ISPListItemAware)value;
				if (valueListItemAware == null) return null;
				int lookupId = valueListItemAware.ListItem.ID;
				string lookupValue = valueListItemAware.ListItem.Name;
				return new SPFieldLookupValue(lookupId, lookupValue);
			}
			return value;
		}
	}
}